﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using BulletSharp;
using DataTypes;

namespace KinectGame
{  

    public class GameObject
    {
        private Model model;

        public Model Model
        {
            get { return model; }
        }
        private BoundingBox box;

        public BoundingBox Box
        {
            get { return box; }
            set { box = value; }
        }

        private Matrix scale = Matrix.Identity;

        public Matrix Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        private Matrix baseRotation = Matrix.Identity;
        public Matrix BaseRotation
        {
            get { return baseRotation; }
            set { baseRotation = value; }
        }

        private Matrix localRotation = Matrix.Identity;
        public Matrix LocalRotation
        {
            get { return localRotation; }
            set { localRotation = value; }
        }

        private Matrix baseTranslation = Matrix.Identity;

        public Matrix BaseTranslation
        {
            get { return baseTranslation; }
            set { baseTranslation = value; }
        }
        
        private Matrix localTranslation = Matrix.Identity;
        public Matrix LocalTranslation
        {
            get { return localTranslation; }
            set { localTranslation = value; }
        }

        private Matrix world = Matrix.Identity;

        public Matrix World
        {
            get 
            {
                if (rigidBody == null)
                {
                    return scale * baseRotation * localRotation * baseTranslation * localTranslation;
                }
                else
                {
                    return scale * rigidBody.WorldTransform;
                }
            }
            set 
            { 
                world = value; 
            }
        }

        private RigidBodyConstructionInfo rigidBodyCI;
        private CollisionShape shape;
        private Vector3 shapeLI;
        private float mass;
        private RigidBody rigidBody = null;

        public RigidBody RigidBody
        {
            get { return rigidBody; }
        }

        public GameObjectType type;
        public bool Visible = false;

        public GameObject(Model model, StaticGameObjectType go)
        {
            this.model = model;
            type = GameObjectType.MODEL;

            this.scale = Matrix.CreateScale(go.scale);
            this.baseRotation = Matrix.CreateRotationX(MathHelper.ToRadians(go.rotation.X)) *
                            Matrix.CreateRotationY(MathHelper.ToRadians(go.rotation.Y)) *
                            Matrix.CreateRotationZ(MathHelper.ToRadians(go.rotation.Z));
            this.baseTranslation = Matrix.CreateTranslation(go.position);
            this.mass = 0.0f;

            shape = new ConvexHullShape(GetVerticesPositionList(model));
            shape.LocalScaling = go.scale;
            shapeLI = shape.CalculateLocalInertia(mass);
        }

        public GameObject(Model model, Vector3 scale, Vector3 rotation, Vector3 translation, float mass)
        {
            this.model = model;
            type = GameObjectType.MODEL;

            this.scale = Matrix.CreateScale(scale);
            this.baseRotation = Matrix.CreateRotationX(MathHelper.ToRadians(rotation.X)) *
                            Matrix.CreateRotationY(MathHelper.ToRadians(rotation.Y)) *
                            Matrix.CreateRotationZ(MathHelper.ToRadians(rotation.Z));
            this.baseTranslation = Matrix.CreateTranslation(translation);
            this.mass = mass;

            shape = new ConvexHullShape(GetVerticesPositionList(model));
            shape.LocalScaling = scale;
            shapeLI = shape.CalculateLocalInertia(mass);
        }

        public GameObject(Model model, Vector3 scale, Vector3 rotation, Vector3 translation, float mass, CollisionShape shape)
        {
            this.model = model;
            type = GameObjectType.MODEL;

            this.scale = Matrix.CreateScale(scale);
            this.baseRotation = Matrix.CreateRotationX(MathHelper.ToRadians(rotation.X)) *
                            Matrix.CreateRotationY(MathHelper.ToRadians(rotation.Y)) *
                            Matrix.CreateRotationZ(MathHelper.ToRadians(rotation.Z));
            this.baseTranslation = Matrix.CreateTranslation(translation);
            this.mass = mass;

            this.shape = shape;

            shape.LocalScaling = scale;
            shapeLI = shape.CalculateLocalInertia(mass);
        }

        public GameObject(Model model, DynamicGameObjectType go, int height)
        {
            this.model = model;
            type = GameObjectType.MODEL;

            this.scale = Matrix.CreateScale(go.scale);
            this.baseRotation = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(RandNumber(0, 90)), MathHelper.ToRadians(RandNumber(0, 90)), /*MathHelper.ToRadians(RandNumber(0, 90))*/0);
            this.baseTranslation = Matrix.CreateTranslation(Vector3.Up * height);
            this.mass = go.mass;

            shape = new ConvexHullShape(GetVerticesPositionList(model));
            shape.LocalScaling = go.scale;
            shapeLI = shape.CalculateLocalInertia(mass);
        }

        public static int RandNumber(int Low, int High)
        {
            Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));

            int rnd = rndNum.Next(Low, High);

            return rnd;
        }

        public GameObject(BoundingBox box, Vector3 scale, Vector3 rotation, Vector3 translation, float mass)
        {
            this.box = box;
            type = GameObjectType.BOX;

            this.scale = Matrix.CreateScale(scale);
            this.localRotation = Matrix.CreateFromYawPitchRoll(rotation.X, rotation.Y, rotation.Z);
            this.localTranslation = Matrix.CreateTranslation(translation);
            
            shape = new BoxShape(GetHalfVectorFromBB(box));
            shapeLI = shape.CalculateLocalInertia(mass);

            this.mass = mass;
        }

        public void Activate()
        {
            Visible = true;
            Matrix tmpWorld = baseRotation * localRotation * baseTranslation * localTranslation;
            rigidBodyCI = new RigidBodyConstructionInfo(mass, new DefaultMotionState(tmpWorld), shape, shapeLI);
            rigidBody = new RigidBody(rigidBodyCI);
            rigidBody.WorldTransform = tmpWorld;

            rigidBody.Activate();
        }        

        private List<Vector3> GetVerticesPositionList(Model model)
        {
            List<Vector3> vertices = new List<Vector3>();

            Matrix[] boneTransforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(boneTransforms);

            int totalNum = 0;
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {

                    int stride = part.VertexBuffer.VertexDeclaration.VertexStride;
                    VertexElement[] elem = part.VertexBuffer.VertexDeclaration.GetVertexElements();
                    int numberv = part.NumVertices;

                    byte[] data = new byte[stride * (numberv + totalNum)];

                    part.VertexBuffer.GetData<byte>(data);

                    for (int i = totalNum * stride; i < data.Length; i += stride)
                    {
                        float x = BitConverter.ToSingle(data, i);
                        float y = BitConverter.ToSingle(data, i + 4);
                        float z = BitConverter.ToSingle(data, i + 8);
                        Vector3 temp = Vector3.Transform(new Vector3(x, y, z), boneTransforms[mesh.ParentBone.Index]);

                        vertices.Add(temp);
                    }
                    totalNum += numberv;
                }
            }

            return vertices;
        }

        private Vector3 GetHalfVectorFromBB(BoundingBox bb)
        {
            Vector3 vec = new Vector3();
            vec.X = Math.Abs(bb.Max.X - bb.Min.X) / 2;
            vec.Y = Math.Abs(bb.Max.Y - bb.Min.Y) / 2;
            vec.Z = Math.Abs(bb.Max.Z - bb.Min.Z) / 2;

            return vec;
        }
    }
}
